/*
 * DatagramBuilder Library: serialize/deserialize data to/from datagram
 * Copyright (C) 2008 Rakov Ilya <RakovIlya@gmail.com>
 *
 * This program can be distributed under the terms of the GNU GPL.
 * See the file COPYING.
 */


/**
 * @file main.cpp
 *
 * @data Dec 2, 2008
 * @author Rakov Ilya
 *
 * @note You need to use "-static" linker option to compile that program,
 * because library "boost_unit_test_framework" has problem with linking process
 */


//#include "../include/Builder.h"
#include "../include/Parser.h"
#include "../include/Mark.h"
#include "../include/EndOfData.h"
#include "../include/ListBuilder.h"
#include "../include/ListParser.h"
#include "../include/SerializeImpl.h"


#define BOOST_TEST_MODULE "C++ Unit Tests for DatagramBuilder"
#include <boost/test/unit_test.hpp>

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/tuple/tuple_comparison.hpp>

#include <algorithm>
#include <string>
#include <sstream>


using boost::tuple;
using boost::tuples::make_tuple;


template <> struct SerializeImpl<     char >: FixedSerializeImpl< uint8_t, 10,     char > {};
template <> struct SerializeImpl<  uint8_t >: FixedSerializeImpl< uint8_t, 11,  uint8_t > {};
template <> struct SerializeImpl<   int8_t >: FixedSerializeImpl< uint8_t, 12,   int8_t > {};
template <> struct SerializeImpl< uint16_t >: FixedSerializeImpl< uint8_t, 13, uint16_t > {};
template <> struct SerializeImpl<  int16_t >: FixedSerializeImpl< uint8_t, 14,  int16_t > {};
template <> struct SerializeImpl< uint32_t >: FixedSerializeImpl< uint8_t, 15, uint32_t > {};
template <> struct SerializeImpl<  int32_t >: FixedSerializeImpl< uint8_t, 16,  int32_t > {};
template <> struct SerializeImpl< std::string >: StringSerializeImpl< uint8_t, 20 > {};


void CheckDump( const Builder &db, const std::string &pattern )
{
  std::stringstream dump;

  dump << db;

  BOOST_CHECK_EQUAL( dump.str(), pattern );
}


template < class T >
void Test( const T constValue1, const std::string &dumpPattern1,
           const T constValue2, const std::string &dumpPattern2 )
{
  BOOST_REQUIRE( constValue1 != constValue2 );

  Builder db;
  Parser  dp( db.GetParser() );

  { // check dump
    T value1( constValue1 ),
      value2( constValue2 );

    db <<      value1
       << constValue1
       << tag(      value2 )
       << tag( constValue2 );

    CheckDump( db, dumpPattern1 + dumpPattern1
                 + dumpPattern2 + dumpPattern2 );
  }

  { // check reading
    T readValue1,
      readValue2;

    dp >> constValue1
       >> readValue1
       >> tag( readValue2 )
       >> tag( constValue2 );

    BOOST_CHECK_EQUAL( readValue1, constValue1 );
    BOOST_CHECK_EQUAL( readValue2, constValue2 );
  }

  { // check const reading
    db <<      constValue1
       << tag( constValue2 )
       << EndOfData();

    BOOST_CHECK_THROW( dp >> EndOfData(),      deserialize_error );
    BOOST_CHECK_THROW( dp >>     constValue2,  deserialize_error );
                       dp >>     constValue1;
    BOOST_CHECK_THROW( dp >> tag(constValue1), deserialize_error );
                       dp >> tag(constValue2);
                       dp >> EndOfData();
  }
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_lexeme )
{
  Builder db;
  Parser dp( db.GetParser() );

  db <<     (    char)1
     << tag(( uint8_t)2)
     <<     (  int8_t)3
     << tag((uint16_t)4)
     <<     ( int16_t)5
     << tag((uint32_t)6)
     <<     ( int32_t)7
     << tag(std::string("eight"))
     << tag(EndOfData());

  CheckDump( db, "1,11,2,3,13,0,4,0,5,15,0,0,0,6,0,0,0,7,20,0,0,0,5,101,105,103,104,116," );

  dp >>     (    char)1
     >> tag(( uint8_t)2)
     >>     (  int8_t)3
     >> tag((uint16_t)4)
     >>     ( int16_t)5
     >> tag((uint32_t)6)
     >>     ( int32_t)7
     >> tag(std::string("eight"))
     >>     EndOfData()
     >> tag(EndOfData());

  CheckDump( db, "1,11,2,3,13,0,4,0,5,15,0,0,0,6,0,0,0,7,20,0,0,0,5,101,105,103,104,116," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_char )
{
  Test<char>( 1,   "1,",
              2,"10,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_uint8_t )
{
  Test<uint8_t>( 1,    "1,",
                 2, "11,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_int8_t )
{
  Test<int8_t>( 1,    "1,",
                2, "12,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_uint16_t )
{
  Test<uint16_t>( 1,    "0,1,",
                  2, "13,0,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_int16_t )
{
  Test<int16_t>( 1,    "0,1,",
                 2, "14,0,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_uint32_t )
{
  Test<uint32_t>( 1,    "0,0,0,1,",
                  2, "15,0,0,0,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_int32_t )
{
  Test<int32_t>( 1,    "0,0,0,1,",
                 2, "16,0,0,0,2," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_string )
{
  Test<std::string>( "abc",   "0,0,0,3,97,98,99,",
                     "12345", "20,0,0,0,5,49,50,51,52,53," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_string_empty1 )
{
  Test<std::string>( "",    "0,0,0,0,",
                     "abc", "20,0,0,0,3,97,98,99," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_string_empty2 )
{
  Test<std::string>( "12345", "0,0,0,5,49,50,51,52,53,",
                     "",      "20,0,0,0,0," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_tuple )
{
  Test< tuple<char, uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, std::string > >(
    make_tuple(1, 3, 5, 7,  9, 11, 13, "odd"),  "1,3,5,0,7,0,9,0,0,0,11,0,0,0,13,0,0,0,3,111,100,100,",
    make_tuple(2, 4, 6, 8, 10, 12, 14, "even"), "10,11,12,13,14,15,16,20,2,4,6,0,8,0,10,0,0,0,12,0,0,0,14,0,0,0,4,101,118,101,110," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_tuple_in_tuple )
{
  Test< tuple<char, uint8_t, tuple< int16_t, std::string, uint32_t >, int32_t, std::string > >(
    make_tuple(1, 3, make_tuple(5, "seven",  9), 11, "odd" ),  "1,3,0,5,0,0,0,5,115,101,118,101,110,0,0,0,9,0,0,0,11,0,0,0,3,111,100,100,",
    make_tuple(2, 4, make_tuple(6, "eight", 10), 14, "even" ), "10,11,14,20,15,16,20,2,4,0,6,0,0,0,5,101,105,103,104,116,0,0,0,10,0,0,0,14,0,0,0,4,101,118,101,110," );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_tuple_with_const )
{
  tuple      <const char, uint8_t, const int16_t, uint32_t, const std::string >
  tuplePattern(        1,       8,             3,        8,             "five" ),
  tupleError0 (        9,       8,             3,        8,             "five" ),
  tupleError3 (        1,       8,             9,        8,             "five" ),
  tupleError4 (        1,       8,             3,        8,             "nine" );

  Builder db;
  Parser dp( db.GetParser() );

                     db << tuplePattern
                        << tuplePattern
                        << tuplePattern
                        << EndOfData();

  BOOST_CHECK_THROW( dp >> EndOfData(), deserialize_error );
  BOOST_CHECK_THROW( dp >> tupleError0, deserialize_error );
                     dp >> tuplePattern;
  BOOST_CHECK_THROW( dp >> EndOfData(), deserialize_error );
  BOOST_CHECK_THROW( dp >> tupleError3, deserialize_error );
                     dp >> tuplePattern;
  BOOST_CHECK_THROW( dp >> EndOfData(), deserialize_error );
  BOOST_CHECK_THROW( dp >> tupleError4, deserialize_error );
                     dp >> tuplePattern;
                     dp >> EndOfData();
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_Parser )
{
  Builder db;

  { // deserialize empty datagram
    Parser dp( db.GetParser() );

    BOOST_CHECK_THROW( dp >> '0', deserialize_error );
  }

  db << 1
     << tag(2)
     << std::string("three")
     << EndOfData();

  Parser dp1( db.GetParser() );

  dp1 >> 1;

  Parser dp2( dp1 );

  BOOST_CHECK_THROW( dp2 >> 1,      deserialize_error );
  BOOST_CHECK_THROW( dp1 >> 1,      deserialize_error );
                     dp2 >> tag(2);
                     dp1 >> tag(2);

  Parser dp3( dp2 );

  BOOST_CHECK_THROW( dp3 >> tag(2), deserialize_error );
  BOOST_CHECK_THROW( dp2 >> tag(2), deserialize_error );
  BOOST_CHECK_THROW( dp1 >> tag(2), deserialize_error );
                     dp3 >> std::string("three") >> EndOfData();
                     dp2 >> std::string("three") >> EndOfData();
                     dp1 >> std::string("three") >> EndOfData();
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_mark )
{
  Builder db;

  Mark<   char>  charMark1(1),  charMark2;
  Mark< int8_t>  int8Mark1(3),  int8Mark2;
  Mark<int16_t> int16Mark1(5), int16Mark2;
  Mark<int32_t> int32Mark1(7), int32Mark2;

  typedef tuple< Mark<int16_t>, const Mark<uint32_t>, std::string > Tuple;

  Tuple tuple1( 10, 20, "thirty" ),
        tuple2(  0, 20 );

  BOOST_CHECK( !charMark1.IsBinded() );
  BOOST_CHECK( !charMark2.IsBinded() );
  BOOST_CHECK( !boost::tuples::get<0>( tuple1 ).IsBinded() );
  BOOST_CHECK( !boost::tuples::get<1>( tuple1 ).IsBinded() );

  BOOST_CHECK_EQUAL( charMark1.Get(), 1 );

  db <<  tag(  charMark1 )
     <<        int8Mark1
     <<       int16Mark1
     <<  tag( int32Mark1 )
     <<       tuple1
     << EndOfData();

  BOOST_CHECK( charMark1.IsBinded() );
  BOOST_CHECK( boost::tuples::get<0>( tuple1 ).IsBinded() );
  BOOST_CHECK( boost::tuples::get<1>( tuple1 ).IsBinded() );

  Parser dp1( db.GetParser() );

  dp1 >> tag( (    char) 1 )
      >>      (  int8_t) 3
      >>      ( int16_t) 5
      >> tag( ( int32_t) 7 )
      >>      Tuple( 10, 20, std::string("thirty") )
      >> EndOfData();

    charMark1.Set( 2 );
    int8Mark1.Set( 4 );
   int16Mark1.Set( 6 );
   int32Mark1.Set( 8 );
   boost::tuples::get<0>( tuple1 ).Set( 20 );

  Parser dp2( db.GetParser() );

  dp2 >> tag( (   char) 2 )
      >>      ( int8_t) 4
      >>      (int16_t) 6
      >> tag( (int32_t) 8 )
      >>      Tuple( 20, 20, std::string("thirty") )
      >> EndOfData();

  Parser dp3( db.GetParser() );

  dp3 >> tag(  charMark2 )
      >>       int8Mark2
      >>      int16Mark2
      >> tag( int32Mark2 )
      >>      tuple2
      >> EndOfData();

  BOOST_CHECK( charMark2.IsBinded() );

  BOOST_CHECK_EQUAL(  charMark2.Get(), 2 );
  BOOST_CHECK_EQUAL(  int8Mark2.Get(), 4 );
  BOOST_CHECK_EQUAL( int16Mark2.Get(), 6 );
  BOOST_CHECK_EQUAL( int32Mark2.Get(), 8 );
  BOOST_CHECK_EQUAL( boost::tuples::get<0>( tuple2 ).Get(), 20 );
  BOOST_CHECK_EQUAL( boost::tuples::get<1>( tuple2 ).Get(), 20 );
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_list_scalar_notag )
{
  typedef std::string elem_t;

  Builder db;

  db << elem_t("before");                                 // serialize before list

  ListBuilder< elem_t, uint32_t > lb( db );               // serialize list

  BOOST_CHECK_EQUAL( lb.GetCount(), 0 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,0," );

  elem_t tmp("4567");

  lb << elem_t("123")
     << tmp;

  BOOST_CHECK_EQUAL( lb.GetCount(), 2 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,2," "0,0,0,3,49,50,51,"
                            "0,0,0,4,52,53,54,55," );

  db << elem_t("after")                                   // serialize after list
     << EndOfData();

  Parser dp( db.GetParser() );

  dp >> elem_t("before");                                 // deserialize before list

  ListParser < elem_t, uint32_t > lp( dp );               // deserialize list
  BOOST_CHECK_EQUAL( lp.GetCount(), 2 );
  BOOST_CHECK( !lp.IsTreated() );

  lp >> tmp
     >> elem_t("4567");

  BOOST_CHECK_EQUAL( tmp, "123" );
  BOOST_CHECK( lp.IsTreated() );
  BOOST_CHECK_THROW( lp >> elem_t(), deserialize_error );

  dp >> elem_t("after")                                   // deserialize after list
     >> EndOfData();
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_list_scalar_tag )
{
  typedef std::string elem_notag_t;
  typedef Tag< elem_notag_t > elem_t;

  Builder db;

  db << std::string("before");                            // serialize before list

  ListBuilder< elem_t, uint32_t > lb( db );               // serialize list

  BOOST_CHECK_EQUAL( lb.GetCount(), 0 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,0," "20,");

  elem_notag_t tmp("4567");

  lb << elem_notag_t("123")
     << tmp;

  BOOST_CHECK_EQUAL( lb.GetCount(), 2 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,2," "20," "0,0,0,3,49,50,51,"
                                  "0,0,0,4,52,53,54,55," );

  db << std::string("after")                              // serialize after list
     << EndOfData();

  Parser dp( db.GetParser() );

  dp >> std::string("before");                            // deserialize before list

  ListParser < elem_t, uint32_t > lp( dp );               // deserialize list
  BOOST_CHECK_EQUAL( lp.GetCount(), 2 );
  BOOST_CHECK( !lp.IsTreated() );

  lp >> tmp
     >> elem_notag_t("4567");

  BOOST_CHECK_EQUAL( tmp, "123" );
  BOOST_CHECK( lp.IsTreated() );
  BOOST_CHECK_THROW( lp >> elem_notag_t(), deserialize_error );

  dp >> std::string("after")                              // deserialize after list
     >> EndOfData();
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_list_tuple_notag )
{
  typedef tuple< std::string, char, int32_t > elem_t;

  Builder db;

  db << std::string("before");                            // serialize before list

  ListBuilder< elem_t, uint32_t > lb( db );               // serialize list

  BOOST_CHECK_EQUAL( lb.GetCount(), 0 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,0," );

  elem_t tmp( "many", '9', 9 );

  lb << elem_t( "one",  '1', 1 )
     << tmp;

  BOOST_CHECK_EQUAL( lb.GetCount(), 2 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,2," "0,0,0,3,111,110,101,"    "49," "0,0,0,1,"
                            "0,0,0,4,109,97,110,121," "57," "0,0,0,9," );

  db << std::string("after")                              // serialize after list
     << EndOfData();

  Parser dp( db.GetParser() );

  dp >> std::string("before");                            // deserialize before list

  ListParser < elem_t, uint32_t > lp( dp );               // deserialize list
  BOOST_CHECK_EQUAL( lp.GetCount(), 2 );
  BOOST_CHECK( !lp.IsTreated() );

  lp >> tmp
     >> elem_t( "many", '9', 9 );

  BOOST_CHECK_EQUAL( tmp, elem_t("one",  '1', 1) );
  BOOST_CHECK( lp.IsTreated() );
  BOOST_CHECK_THROW( lp >> elem_t(), deserialize_error );

  dp >> std::string("after")                              // deserialize after list
     >> EndOfData();
}


BOOST_AUTO_TEST_CASE( DatagramBuilder_list_tuple_tag )
{
  typedef tuple< std::string, char, int32_t > elem_notag_t;
  typedef Tag< elem_notag_t >                 elem_t;

  Builder db;

  db << std::string("before");                            // serialize before list

  ListBuilder< elem_t, uint32_t > lb( db );               // serialize list

  BOOST_CHECK_EQUAL( lb.GetCount(), 0 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,0," "20,10,16," );

  elem_notag_t tmp( "many", '9', 9 );

  lb << elem_notag_t( "one",  '1', 1 )
     << tmp;

  BOOST_CHECK_EQUAL( lb.GetCount(), 2 );
  CheckDump( db, "0,0,0,6,98,101,102,111,114,101,"
                 "0,0,0,2," "20,10,16," "0,0,0,3,111,110,101,"    "49," "0,0,0,1,"
                                        "0,0,0,4,109,97,110,121," "57," "0,0,0,9," );

  db << std::string("after")                              // serialize after list
     << EndOfData();

  Parser dp( db.GetParser() );

  dp >> std::string("before");                            // deserialize before list

  ListParser < elem_t, uint32_t > lp( dp );               // deserialize list
  BOOST_CHECK_EQUAL( lp.GetCount(), 2 );
  BOOST_CHECK( !lp.IsTreated() );

  lp >> tmp
     >> elem_notag_t( "many", '9', 9 );

  BOOST_CHECK_EQUAL( tmp, elem_notag_t("one",  '1', 1) );
  BOOST_CHECK( lp.IsTreated() );
  BOOST_CHECK_THROW( lp >> elem_notag_t(), deserialize_error );

  dp >> std::string("after")                              // deserialize after list
     >> EndOfData();
}
